using graver.CodeAnalysis.Binding;

namespace graver.CodeAnalysis;

/// <summary>
/// 计算器
/// </summary>
internal sealed class Evaluator
{
    private readonly BoundExpression _root;


    public Evaluator(BoundExpression root)
    {
        this._root = root;
    }

    /// <summary>
    /// 计算器入口
    /// </summary>
    /// <returns>计算结果</returns>
    public object Evaluate()
    {
        return EvaluateExpression(_root);
    }

    /// <summary>
    /// 实际的计算器入口
    /// </summary>
    /// <param name="node">语法树根节点</param>
    /// <returns>计算结果</returns>
    private object EvaluateExpression(BoundExpression node)
    {
        if (node is BoundLiteralExpression n)
        {
            return n.LiteralValue;
        }

        if (node is BoundUnaryExpression u)
        {
            var operand = EvaluateExpression(u.Operand);
            return u.Op.Kind switch
            {
                BoundUnaryOperatorKind.Identity => (int)operand,
                BoundUnaryOperatorKind.Negation => -(int)operand,
                BoundUnaryOperatorKind.LogicNegation => !(bool)operand,
                _ => throw new Exception($"Unexpected unary operator {u.Op}")
            };
        }

        if (node is BoundBinaryExpression bin)
        {
            var left = EvaluateExpression(bin.Left);
            var right = EvaluateExpression(bin.Right);

            return bin.Op.Kind switch
            {
                BoundBinaryOperatorKind.Addition => (int)left + (int)right,
                BoundBinaryOperatorKind.Subtraction => (int)left - (int)right,
                BoundBinaryOperatorKind.Multiplication => (int)left * (int)right,
                BoundBinaryOperatorKind.Division => (int)left / (int)right,
                BoundBinaryOperatorKind.LogicAnd => (bool)left && (bool)right,
                BoundBinaryOperatorKind.LogicOr => (bool)left || (bool)right,
                BoundBinaryOperatorKind.Equals => Equals(left, right),
                BoundBinaryOperatorKind.NotEquals => !Equals(left, right),
                _ => throw new Exception($"Unexpected binary operator {bin.Op}")
            };
        }

        throw new Exception($"Unexpected node {node.Kind}");
    }
}
